package tsproxy

import (
	"encoding/json"
	"errors"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"github.com/influxdata/influxql"
)

const (
	defaultMemory = 32 << 20 // 32 MB
	indexPage     = "index.html"
	defaultIndent = "  "

	HeaderContentType = "Content-Type"

	MIMEApplicationJSON = "application/json"
	MIMETextPlain       = "text/plain"
	MIMEMultipartForm   = "multipart/form-data"
	MIMEOctetStream     = "application/octet-stream"
	MIMEApplicationForm = "application/x-www-form-urlencoded"
)

func Run(listenAddr, moURL, influxURL string) error {
	u, err := url.Parse(influxURL)
	if err != nil {
		return err
	}
	proxy := httputil.NewSingleHostReverseProxy(u)
	srv := &server{
		proxy: proxy,
		Rewrite: Rewrite{
			RewriteMoQueryExpr: RewriteMoQueryExpr(moURL),
		},
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/query", srv.influxQuery)
	return http.ListenAndServe(listenAddr, mux)
}

type Response struct {
	Results interface{} `json:"results,omitempty"`
	Err     string      `json:"error,omitempty"`
}

func httpError(w http.ResponseWriter, errmsg string, code int) {
	w.WriteHeader(code)
	w.Header().Set(HeaderContentType, MIMEApplicationJSON)
	err := json.NewEncoder(w).Encode(Response{Err: errmsg})
	if err != nil {
		log.Println("write error body error:", err)
	}
}

type server struct {
	proxy   *httputil.ReverseProxy
	Rewrite Rewrite
}

func (srv *server) influxQuery(w http.ResponseWriter, r *http.Request) {
	var params url.Values
	if r.Method == http.MethodGet {
		params = r.URL.Query()
	} else if strings.HasPrefix(r.Header.Get(HeaderContentType), MIMEMultipartForm) {
		if err := r.ParseMultipartForm(defaultMemory); err != nil {
			httpError(w, "error parsing query statements: "+err.Error(), http.StatusBadRequest)
			return
		}
		params = r.PostForm
	} else {
		if err := r.ParseForm(); err != nil {
			httpError(w, "error parsing query statements: "+err.Error(), http.StatusBadRequest)
			return
		}
		params = r.PostForm
	}

	s := params.Get("q")
	p := influxql.NewParser(strings.NewReader(s))

	// Parse the parameters
	rawParams := params.Get("params")
	if rawParams != "" {
		var params map[string]interface{}
		decoder := json.NewDecoder(strings.NewReader(rawParams))
		decoder.UseNumber()
		if err := decoder.Decode(&params); err != nil {
			httpError(w, "error parsing query parameters: "+err.Error(), http.StatusBadRequest)
			return
		}

		// Convert json.Number into int64 and float64 values
		for k, v := range params {
			if v, ok := v.(json.Number); ok {
				var err error
				if strings.Contains(string(v), ".") {
					params[k], err = v.Float64()
				} else {
					params[k], err = v.Int64()
				}

				if err != nil {
					httpError(w, "error parsing json value: "+err.Error(), http.StatusBadRequest)
					return
				}
			}
		}
		p.SetParams(params)
	}

	stmt, err := p.ParseStatement()
	if err != nil {
		httpError(w, "error parsing query statements: "+err.Error(), http.StatusBadRequest)
		return
	}

	convertedStmt, err := srv.transformInfluxQL(stmt)
	if err != nil {
		httpError(w, "error parsing query statements: "+err.Error(), http.StatusBadRequest)
		return
	}
	params.Set("q", convertedStmt.String())

	if r.Method == http.MethodGet {
		r.URL.RawQuery = params.Encode()
	} else {
		r.Body = io.NopCloser(strings.NewReader(params.Encode()))
	}
	srv.proxy.ServeHTTP(w, r)
}

func (srv *server) transformInfluxQL(stmt influxql.Statement) (influxql.Statement, error) {
	selectStmt, ok := stmt.(*influxql.SelectStatement)
	if !ok {
		return stmt, nil
	}

	// 重写 where
	condition, err := srv.Rewrite.Rewrite(selectStmt.Condition)
	if err != nil {
		return nil, err
	}
	selectStmt.Condition = condition

	// 重写 字段 ?
	// ...........

	return selectStmt, nil
}

func errWrap(err error, errmsg string) error {
	return errors.New(errmsg + ": " + err.Error())
}
